if (!window["vg"]) {
    window["vg"] = {};
}

if (!vg.visualization) {
    vg.visualization = {};
}

vg.visualization.OrgChart = (function() {
	var nodePositions = {
		"LEFTMOST" : "0",
		"BETWEEN" : "1",
		"RIGHTMOST" : "2",
		"ONENODE" : "3"
	};

	var positionClasses = [
		["", "top left"],
		["top", "top left"],
		["top", "left"],
		["", "left"]
	];

	var classes = {
		"noderow" : "noderow",
		"node" : "node",
		"childStub" : "left",
		"chart" : "vg-orgchart",
		"noderow" : "noderow"
	};


	var createNode = function(nodeType, inputObj) {
		var element = document.createElement(nodeType);
		if(inputObj) {
			inputObj["className"] && inputObj["className"].length > 0 && (element.className = inputObj["className"]);
			if(inputObj["attributes"]) {
				for(var i in inputObj["attributes"]) {
					element.setAttribute(i, inputObj["attributes"][i]);
				}
	 		}
	 		if(inputObj["parent"]) {
	 			inputObj["parent"].appendChild(element);
	 		}
		}
		return element;
	};

	//Inner node class
	var Node = function(inputObj)	 {
		if(inputObj.name == "" || !inputObj.name) {
			throw "Node name not provided for the node";
		}
		this.name = inputObj["name"];
		this.tooltip = inputObj["tooltip"] || "";
		this.data = inputObj["data"] || undefined;
		this.nodePosition = inputObj["nodePosition"];
		this.parentNode = this.setParentNode(inputObj["parentNode"]);
		this.constructor = arguments.callee;
	};

	Node.prototype = (function () {
		var setParentNode = function (parentNodeObj) {
			this.parentNode = parentNodeObj;
			this.isRootNode = !parentNodeObj;
			this.nodeDepth = parentNodeObj ? (parentNodeObj.nodeDepth + 1) : 0;
		};

		var setChildNodes = function (childNodes, leafNodeCount) {
			if(childNodes.length == 0) {
				this.isLeafNode = true;
				this.childNodes = undefined;
			} else {
				this.isLeafNode = false;
				this.childNodes = childNodes;
			}
		};

		var setDimensions = function(leafNodeCount, index, depthMap) {
			this.leafNodeCount = !this.childNodes ? 1 : leafNodeCount;
			var targetSize = this.leafNodeCount * 8;
			this.dimensionMatrix = getDimensionMatrix(targetSize);
			this.depthMap = depthMap;
			setLeafNodeIndices(this, index);
			leftSideCorrection(this, depthMap[depthMap.length - 1]);
			!this.isLeafNode && centralizeNode(this);
		};

		var setLeafNodeIndices = function(nodeObj, index) {
			if(nodeObj.isLeafNode) {
				nodeObj.startLeafIndex = index;
				nodeObj.endLeafIndex = index;
			} else {
				nodeObj.startLeafIndex = nodeObj.childNodes[0].startLeafIndex;
				nodeObj.endLeafIndex = nodeObj.childNodes.slice(-1)[0].endLeafIndex;
			}
		};

		var leftSideCorrection = function(currentNode, previousNode) {
			var toAdd = (currentNode.startLeafIndex - (previousNode ? previousNode.endLeafIndex : 0) - 1) * 8;
			toAdd > 0 && currentNode.dimensionMatrix.forEach(function(elmt, i, arr){
				elmt[0] = elmt[0] + toAdd;
			});
		};

		var getNodeWidth = function() {
			return this.dimensionMatrix[1].reduce(function(a,b) {
				return a+b;
			});
		};

		var centralizeNode = function(nodeObj) {
			if(nodeObj.name == 'd') {
				debugger;
			}
			var firstChild = nodeObj.childNodes[0];
			var lastChild = nodeObj.childNodes.slice(-1)[0];
			var distObj = getDistanceBetween(firstChild, lastChild);
			var currentPosition = getDistanceFromStart(nodeObj)["distance"];
			var targetPosition = distObj["fromNodeDistance"] + distObj["distanceBetween"]/2;
			var offset = targetPosition - currentPosition;
			offset > 0 && addOffset(nodeObj, offset);
		};

		var getDistanceBetween = function(fromNode, toNode) {
			var distObj = getDistanceFromStart(fromNode);
			var startIndex = distObj["index"];
			var depthMap = toNode.depthMap;
			var distBetween = -(fromNode.dimensionMatrix[1][0]);
			for(i = startIndex; (i < depthMap.length && depthMap[i] != toNode); i++) {
				distBetween += depthMap[i].getNodeWidth();
			}
			distBetween += toNode.dimensionMatrix[1][0];
			return {
				"fromNodeDistance" : distObj["distance"],
				"distanceBetween" : distBetween
			};
		};

		var getDistanceFromStart = function(node) {
			var distance = 0;
			var depthMap = node.depthMap;
			for(var i =0; (i < depthMap.length && depthMap[i] != node); i++) {
				distance += depthMap[i].getNodeWidth();
			}
			distance += node.dimensionMatrix[1][0];
			return {
				"distance" : distance,
				"index" : i
			};
		};

		var addOffset = function(nodeObj, offset) {
			nodeObj.dimensionMatrix[0][0] += offset;
			nodeObj.dimensionMatrix[0][1] -= offset;
			nodeObj.dimensionMatrix[1][0] += offset;
			nodeObj.dimensionMatrix[1][2] -= offset;
			nodeObj.dimensionMatrix[2][0] += offset;
			nodeObj.dimensionMatrix[2][1] -= offset;
		};

		var getTotalDimension = function(nodes) {
			var totalSize = nodes.reduce(function(a,b) {
				var aSize = a.constructor == Node ? a.getNodeWidth() : a;
				return aSize + b.getNodeWidth();
			});
			return totalSize;
		};

		var getDimensionMatrix = function(targetSize) {
			var connectionSize = targetSize/2;
			var nodePadding = (targetSize - 6)/2;
			return [
				[connectionSize, connectionSize],
				[nodePadding,6,nodePadding],
				[connectionSize, connectionSize]
			];
		};

		var getHTMLContent = function() {
			this.htmlContent = [];
			for(var i = 0; i < this.dimensionMatrix.length; i++) {
				this.htmlContent[i] = [];
				for(var j = 0; j < this.dimensionMatrix[i].length; j++) {
					var className = "";
					if(i == 0 && !this.isRootNode) {
						className = positionClasses[this.nodePosition][j];
					} else if(i == 1 && j == 1) {
						className = classes["node"];
					} else if (i == 2 && j == 1 && !this.isLeafNode) {
						className = classes["childStub"];
					}
					var styleString = className.length > 0 ? " class = '" + className + "'" : "";
					this.htmlContent[i][j] = "<td colspan = '" + this.dimensionMatrix[i][j] + "'" + styleString + ">" + ((j == 1 && i == 1) ? this.name : "") + "</td>";
				}
			}
			return this.htmlContent;
		};

		return {
			"setChildNodes" : setChildNodes,
			"setParentNode" : setParentNode,
			"getHTMLContent" : getHTMLContent,
			"getNodeWidth" : getNodeWidth,
			"setDimensions" : setDimensions
		};
	})();

	//Property declarations
	var orgchart = function(inputObj) {
		this.container = inputObj.container;
		this.currentLeafNodeIndex = 0;
		this.tableRows = [];
		this.table = createTable();
	};

	var createTable = function() {
		var table = createNode("table", {
			"className" : classes["chart"],
			"attributes" : {
				"cellspacing" : "0"
			}
		});
		var tbody = createNode("tbody", {
			"parent" : table
		});
		return table;
	}

	var arrangeData = function(chartObj) {
		var rootNode;
		for(var i = 0; i < chartObj.userData.length; i++) {
			var parent = chartObj.userData[i][1];
			var child = chartObj.userData[i][0];
			if(parent == child) {
				rootNode = chartObj.userData[i];
				continue;
			}			
			!chartObj.parentToChildMap[parent] && (chartObj.parentToChildMap[parent] = []);
			chartObj.parentToChildMap[parent].push(chartObj.userData[i]);
		}
		return rootNode;
	};

	var createNodes = function(parentNodeObj, nodeData, level, nodePos) {
		var childNodes = this.parentToChildMap[nodeData[0]] || [];
		var currentNode = new Node({
			"name" : nodeData[0],
			"tooltip" : nodeData[2] || "",
			"data" : nodeData[3] || undefined,
			"parentNode" : parentNodeObj,
			"nodePosition" : nodePos
		});		
		var childLevel = level + 1;
		var childNodeObjects = [];
		var leafNodeCount = 0;
		for(var i = 0; i < childNodes.length; i++) {
			if (childNodes.length == 1) {
				var childNodePosition = "ONENODE";
			} else if (i == 0) {
				childNodePosition = "LEFTMOST";
			} else if (i == childNodes.length - 1) {
				childNodePosition = "RIGHTMOST";
			} else {
				childNodePosition = "BETWEEN";
			}
			var childNode = arguments.callee.call(this, currentNode, childNodes[i], childLevel, nodePositions[childNodePosition]);
			leafNodeCount += childNode.leafNodeCount;
			childNodeObjects.push(childNode);
		}
		currentNode.setChildNodes(childNodeObjects);
		!this.depthToLeafNodeMap[currentNode.nodeDepth] && (this.depthToLeafNodeMap[currentNode.nodeDepth] = []);
		var currentMap = this.depthToLeafNodeMap[currentNode.nodeDepth];
		currentNode.isLeafNode && (this.currentLeafNodeIndex = this.currentLeafNodeIndex + 1);
		currentNode.setDimensions(leafNodeCount, this.currentLeafNodeIndex, currentMap);
		currentMap.push(currentNode);
		addToRow(currentNode, this.tableRows);
		return currentNode;
	};

	var addToRow = function(nodeObj, rows) {
		var htmlMatrix = nodeObj.getHTMLContent();
		if(!rows[nodeObj.nodeDepth]) {
			rows[nodeObj.nodeDepth] = ["","",""];
		}
		var currentArray = rows[nodeObj.nodeDepth];
		for(var i = 0; i < htmlMatrix.length; i++) {
			currentArray[i] += htmlMatrix[i].join("");
		}
	};

	orgchart.prototype = (function() {
		var setData = function(data) {
			this.userData = data;
			this.parentToChildMap = {};
			this.depthToLeafNodeMap = {};
			this.rootNodeData = arrangeData(this);
			if(!this.rootNodeData) {
				throw "Root Node missing for the tree";
			}
			this.rootNode = createNodes.call(this, undefined, this.rootNodeData, 0);		
		};

		var draw = function() {
			var bodyString = "<tr>";
			var totalCellCount = this.currentLeafNodeIndex * 8;
			for(var i = 0; i < totalCellCount; i++) {
				bodyString += "<td/>";
			}
			bodyString += "</tr>";
			for(i = 0; i < this.tableRows.length; i++) {
				for(var j = 0; j < this.tableRows[i].length; j++) {
					bodyString += "<tr" + (j == 1 ? " class = '" + classes["noderow"] + "'" : "") + ">" + this.tableRows[i][j] + "</tr>";
				}
			}
			this.table.childNodes[0].innerHTML = bodyString;		
			this.container.appendChild(this.table);
		};

		return {
			"setData" : setData,
			"draw"  : draw
		};
	})();

	(function init() {
		var cssText = ".vg-orgchart td {font-size: 10px;text-align: center;width : 20px;}.vg-orgchart tr {height : 10px;}.noderow {height : 60px !important;}.node {border : 1px solid black;}.top {border-top: 1px solid black;}.left {border-left: 1px solid black;}";
		var styleTag = createNode("style");
		styleTag.innerText = cssText;
		document.getElementsByTagName("head")[0].appendChild(styleTag);
	})();
	return orgchart;
})();